﻿namespace FSpec.Runner

open System
open System.Reflection 
open Gallio.Common.Reflection 
open Gallio.Model.Helpers 
open Gallio.Model
open FSpec.Runner.Model

[<AutoOpen>]
module ExplorerHelper = 
    let createContextNodeFrom (context:ITypeInfo * seq<Specification>) = 
        let typeInfo, testCases = context //I <3 tuple decomp in F#
        let contextNode = new Context(typeInfo.Name, typeInfo)
        for testCase in testCases do
            contextNode.AddChild testCase
        contextNode
    
    let createConcernNodeFrom (concern:INamespaceInfo * seq<Context>) =
        let namespaceInfo, contexts = concern
        let concernNode = new Concern(namespaceInfo.Name, namespaceInfo)
        for context in contexts do
            concernNode.AddChild context
        concernNode
    
    let buildTestModelFrom (parentNode:Tree.Test) (testCases:seq<Specification>) =
        testCases 
            |> Seq.groupBy(fun testCase -> ReflectionUtils.GetType testCase.CodeElement)
            |> Seq.map(fun context -> createContextNodeFrom context)
            |> Seq.iter(fun concernNode -> parentNode.AddChild concernNode)
    
    let publicStaticInstance = BindingFlags.Public ||| BindingFlags.Static ||| BindingFlags.Instance

    let (|Assembly|Namespace|Type|Method|Unused|) (codeElement:ICodeElementInfo) =
        match codeElement.Kind with
        | CodeElementKind.Assembly -> Assembly (codeElement :?> IAssemblyInfo)
        | CodeElementKind.Namespace -> Namespace (codeElement :?> INamespaceInfo)
        | CodeElementKind.Type -> Type (codeElement :?> ITypeInfo)
        | CodeElementKind.Method -> Method (codeElement :?> IMethodInfo)
        | _ -> Unused (codeElement)

    let getTestCasesFrom codeElement =
        let rec explore (ce:ICodeElementInfo) =
            match ce with 
            | Method md     -> seq [ new Specification(md.Name, md) ]
            | Type ty       -> seq [ 
                                for md in ty.GetMethods publicStaticInstance do
                                    if conventions |> List.exists (fun c -> md.Name.StartsWith c) then 
                                        yield explore md 
                               ] |> Seq.concat
            | Assembly asm  -> seq [ 
                                for ty in asm.GetExportedTypes() do
                                    yield explore ty
                               ]  |> Seq.concat 
            | Namespace ns  -> let asm = ReflectionUtils.GetAssembly ns
                               explore asm 
            | Unused u      -> let asm = ReflectionUtils.GetAssembly u
                               explore asm
        explore codeElement